package demo.java.lang.clas;

import demo.spring.service.IActions;
import demo.vo.People;
import demo.vo.Person;
import demo.vo.json.Student;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ClassDemo {

    private static Logger logger = LoggerFactory.getLogger(ClassDemo.class);

    @Test
    public void testCollection() {
        Class<?> clsList = List.class;
        Class<?> clsCollection = Collection.class;
        logger.info(clsList.toGenericString() + " #" + clsList.toString());
        logger.info("isArray = {}", clsList.isArray());
        logger.info("isInterface = {}", clsList.isInterface());
        logger.info("isInstance = {}", clsList.isInstance(Collections.emptyList()));
        logger.info("cls.isAssignableFrom(Collection.class) = {}", clsList.isAssignableFrom(Collection.class));
        logger.info("clsCollection.isAssignableFrom(List.class) = {}", clsCollection.isAssignableFrom(List.class));
    }


    @Test
    public void testLong() {
        Class<?> cls = Long.class;
        System.out.println(cls.toGenericString() + " #" + cls.toString());
        cls = long.class;
        System.out.println(cls.toGenericString() + " #" + cls.toString());
        cls = Long.TYPE;
        System.out.println(cls.toGenericString() + " #" + cls.toString());

        String canonicalName = cls.getCanonicalName();
        System.out.println("canonicalName = " + canonicalName);
    }

    /**
     *
     */
    @Test
    public void printFilePath() {

        System.out.println(ClassDemo.class.getClassLoader().getResource("").getPath());
    }

    /**
     * 获取资源的路径
     * <p>
     * Class.getResource(String path): path 不以’/'开头时，默认是从此类所在的包下取资源；path 以’/'开头时，则是从ClassPath根下获取；
     * <p>
     * Class.getResource和Class.getResourceAsStream在使用时，路径选择上是一样的。
     * <p>
     * Class.getClassLoader().getResource(String path):path不能以’/'开头时；path是从ClassPath根下获取；
     * <p>
     * Class.getClassLoader().getResource和Class.getClassLoader().getResourceAsStream在使用时，路径选择上也是一样的。
     */
    @Test
    public void getResource() {
        System.out.println("path 以’/'开头时，则是从ClassPath根下获取:" + ClassDemo.class.getResource("/").getPath());
        System.out.println("path 不以’/'开头时，默认是从此类所在的包下取资源:" + ClassDemo.class.getResource("").getPath());

        System.out.println("path不能以’/'开头时:" + ClassDemo.class.getClassLoader().getResource("/"));
        System.out.println("path是从ClassPath根下获取:" + ClassDemo.class.getClassLoader().getResource("").getPath());
    }


    /**
     * 正确的强转
     */
    @Test
    public void testRightCase() {
        Person person = new Student();
        person.setLastName("HAN");
        Student student = (Student) person;
        System.out.println(student.getLastName());
    }

    /**
     * 错误的强转
     */
    @Test
    public void testWrongCase() {
        Person person = new Person();
        person.setLastName("HAN");
        Student student = (Student) person;
        System.out.println(student.getLastName());
    }

    /**
     * getComponentType，打印数组所属类型
     */
    @Test
    public void getComponentType() {
        int[] intArray = {1, 2, 3};
        Class<?> cls = intArray.getClass().getComponentType();
        System.out.println("整型数组getComponentType: " + cls.getName());

        List<String> list = Arrays.asList("a");
        cls = list.getClass().getComponentType();
        System.out.println("如果不是数组，getComponentType返回null，如List： " + cls);

        String[] arrayString = {};
        cls = arrayString.getClass().getComponentType();
        System.out.println("空字符串数组getComponentType : " + cls);
    }

    /**
     * 获取类所实现的接口
     */
    @Test
    public void testInterface() {
        Class<?> cls = People.class;
        System.out.println(cls.toGenericString());
        Class<?>[] interfaces = cls.getInterfaces();
        System.out.println("--------------getInterfaces");
        for (Class<?> class1 : interfaces) {
            System.out.println(class1.toGenericString());
        }
        System.out.println("--------------getGenericInterfaces");
        Type[] types = cls.getGenericInterfaces();
        for (Type type : types) {
            System.out.println(type.getTypeName() + " : " + type.toString());
        }

        System.out.println("--------------getAnnotatedInterfaces");
        AnnotatedType[] annotatedTypes = cls.getAnnotatedInterfaces();
        for (AnnotatedType annotatedType : annotatedTypes) {
            System.out.println(annotatedType.getType().getTypeName() + " : " + annotatedType.toString());
        }

    }


    /**
     * class1.isAssignableFrom(class2) 判定此 Class对象所表示的类或接口与指定的 Class参数所表示的类或接口是否相同，
     * 或是否是其超类或超接口。 如果是则返回true；否则返回false。
     * 如果该 Class 表示一个基本类型，且指定的 Class 参数正是该 Class 对象，则该方法返回 true；否则返回 false。
     */
    @Test
    public void isAssignableFrom() {
        // true
        System.out.println(List.class.isAssignableFrom(List.class));
        System.out.println(Collection.class.isAssignableFrom(List.class));
        System.out.println(Object.class.isAssignableFrom(Collection.class));
        System.out.println(Object.class.isAssignableFrom(List.class));
        // false
        System.out.println(List.class.isAssignableFrom(Collection.class));
        System.out.println(Collection.class.isAssignableFrom(Object.class));
        System.out.println(List.class.isAssignableFrom(Object.class));
    }

    /**
     * obj.instanceof(class) 也就是说这个对象是不是这种类型，
     * <li>1.一个对象是本身类的一个对象
     * <li>2.一个对象是本身类父类（父类的父类）和接口（接口的接口）的一个对象
     * <li>3.所有对象都是Object
     * <li>4.凡是null有关的都是false null.instanceof(class)
     *
     * <p>
     * class.isInstance(obj) 这个对象能不能被转化为这个类
     * <li>1.一个对象是本身类的一个对象
     * <li>2.一个对象能被转化为本身类所继承类（父类的父类等）和实现的接口（接口的父接口）强转
     * <li>3.所有对象都能被Object的强转
     * <li>4.凡是null有关的都是false class.inInstance(null)
     * <p>
     * 类名.class和对象.getClass()几乎没有区别，因为一个类被类加载器加载后，就是唯一的一个类
     */
    @Test
    public void testIsInstance() {
        People p = new People();
        IActions actions = p;
        logger.info("actions instanceof People = {}", actions instanceof People);

        logger.info("People.class.isInstance(p) = {}", People.class.isInstance(p));
        logger.info("p instanceof People = {}", p instanceof People);

        logger.info("Serializable.class.isInstance(p) = {}", Serializable.class.isInstance(p));
        logger.info("IActions.class.isInstance(p) = {}", IActions.class.isInstance(p));
        logger.info("Cloneable.class.isInstance(p) = {}", Cloneable.class.isInstance(p));
        logger.info("Object.class.isInstance(p) = {}", Object.class.isInstance(p));
        logger.info("Map.class.isInstance(p) = {}", Map.class.isInstance(p));

        logger.info("Long.class.isInstance(null) = {}", Long.class.isInstance(null));
        logger.info("Long.class.isInstance(\"\") = {}", Long.class.isInstance(""));
        logger.info("Long.class.isInstance(\"1\") = {}", Long.class.isInstance("1"));
        logger.info("Long.class.isInstance(6) = {}", Long.class.isInstance(6));
        logger.info("Long.class.isInstance(6L) = {}", Long.class.isInstance(6L));
    }

    /**
     * instanceof 增加了模式匹配的功能，如果变量类型经过instanceof判断能够匹配目标类型，则对应分支中无需再做类型强转。
     */
    @Test
    public void testInstanceOf() {
        People p = new People();
        IActions actions = p;
        System.out.println("actions instanceof People = " + (actions instanceof People));
        System.out.println("p instanceof People = " + (p instanceof People));
        List<String> list = new ArrayList<>();
        System.out.println("list instanceof Collection = " + (list instanceof Collection));
        Set<Object> set = new HashSet<>();
        System.out.println("set instanceof Collection = " + (set instanceof Collection));

        // JDK17
        Object o = 100;
        if (o instanceof Integer i && i > 0) {
            System.out.println(i.intValue());
        } else if (o instanceof String s && s.startsWith("t")) {
            System.out.println(s.charAt(0));
        }

        Object str = "hello world!";
        if (str instanceof String s && !s.isEmpty()) {
            System.out.println(s.toUpperCase());
        }

    }


    @Test
    public void getMethod() throws NoSuchMethodException, SecurityException {
        Method m1[] = Long.class.getMethods();
        System.out.println("1、返回类所有的公共成员方法：");
        for (int i = 0; i < m1.length; i++) {
            System.out.println(m1[i].toString());
        }

        Method m2 = Long.class.getMethod("longValue", new Class[]{});
        System.out.println("2、返回指定公共成员方法：");
        System.out.println(m2.toString());
    }

    @Test
    public void testForName() {
        String className = "demo.java.lang.clas.ClassDemo";
        try {
            System.out.println("默认初始化 类");
            Class clas = Class.forName(className);
            System.out.println(clas);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        try {
            System.out.println("不 初始化 类");
            Class clas = Class.forName(className, false, this.getClass().getClassLoader());
            System.out.println(clas);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    static {
        System.out.println("静态代码初始化！");
    }

    @Test
    public void testClassNotFound() {
        ClassNotFoundException classNotfoundException = new ClassNotFoundException();
        NoClassDefFoundError noClassDefFoundError = new NoClassDefFoundError();
    }

    @Test
    public void testRecord() {
        Point p = new Point(10, 20);
        for (Method method : p.getClass().getMethods()) {
            System.out.println(method);
        }
        for (Field field : p.getClass().getDeclaredFields()) {
            System.out.println(field);
            // 不允许通过反射修改值。
            // field.setAccessible(true);
            // field.set(p,30);
        }
        System.out.println(p.x() + "====" + p.y());
    }

    /**
     * 隐藏类是一种不能被其他类直接使用的类。
     * 隐藏类不再依赖于类加载器，而是通过读取目标类字节码的方式，创建一个对其他类字节码隐藏的class对象，然后通过反射的方式创建对象，调用方法。
     * <p>
     * 传统方式下，要使用这个类，就需要经过编译，然后类加载的整个过程。
     * 但是隐藏类机制允许直接从编译后的class字节码入手，并且绕过整个类加载的复杂过程，直接使用这个类。
     */
    @Test
    public void demoHiddenClass() throws Throwable {

        //编译后的 class 文件地址
        String classPath = "/Users/xmly/Documents/code/Demo/testJava/target/classes/demo/java/lang/clas/HiddenClass.class";
        byte[] bytes = Files.readAllBytes(Paths.get(classPath));
        String base64Str = Base64.getEncoder().encodeToString(bytes);
        System.out.println(base64Str);

        //class文件的字节码
        String CLASS_INFO = base64Str;
        byte[] classInBytes = Base64.getDecoder().decode(CLASS_INFO);
        Class<?> proxy = MethodHandles.lookup()
                .defineHiddenClass(classInBytes, true, MethodHandles.Lookup.ClassOption.NESTMATE)
                .lookupClass();

        // 输出类名
        System.out.println(proxy.getName());
        // 输出类有哪些函数
        for (Method method : proxy.getDeclaredMethods()) {
            System.out.println(method.getName());
        }
        // 2. 调用对应的方法
        MethodHandle mhPrintHello = MethodHandles.lookup().findStatic(proxy, "printHello", MethodType.methodType(void.class, String.class));
        mhPrintHello.invokeExact("孙大圣");
        Object proxyObj = proxy.getConstructors()[0].newInstance();
        MethodHandle mhSayHello = MethodHandles.lookup().findVirtual(proxy, "sayHello", MethodType.methodType(String.class, String.class));
        System.out.println(mhSayHello.invoke(proxyObj, "孙行者"));

    }

    /**
     * 在 JDK17 中，可以声明一种特殊的类，record 。被reocrd定义的类代表的是一种不可变的常量，只能用来描述一种简单的不可变的数据结构。
     * 这样我们未来或许就不用再定义一大堆的 BO、 VO 、 DTO 这些只用来进行值传递的复杂对象了。
     */
    public record Point(int x, int y) {
    }


}

/**
 * 非密封子类，可以随意继承
 */
non-sealed class Square extends Shape {
    @Override
    public int lines() {
        return 4;
    }
}

/**
 * final 子类，不可再被继承
 */
final class Circle extends Shape {
    @Override
    public int lines() {
        return 0;
    }
}

/**
 * sealed 表示这个子类有密封特性。密封子类，继续声明他所允许的子类。
 */
sealed class Rectangle extends Shape permits FilledRectangle {
    @Override
    public int lines() {
        return 3;
    }
}

final class FilledRectangle extends Rectangle {
    @Override
    public int lines() {
        return 0;
    }
}

/**
 * 密封类 Sealed Classes.
 * 在 JDK8 中，每一个类都可以被任意多个子类继承，并修改其中的内置功能。
 * 比如 JDK8 中最重要的类加载双亲委派机制，在应用当中，程序员可以随意挑选一个内置的类加载器，继承出新的类加载器实现，随意打破双亲委派机制。
 * 这其实是不太安全的，意味着很多内置的行为得不到保护。而密封类就是用来限制每一个父类可以被哪些子类继承或者实现。
 * <p>
 * 在声明类或方法时，如果增加sealed修饰，那么还需要同时增加permits指定这个类可以被哪些类来继承或实现
 */
sealed abstract class Shape permits Circle, Rectangle, Square {

    public abstract int lines();
}
